﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Text;
using System.Xml;

namespace CARMSBusiness
{

    public class TaskConfiguration
    {
        private const string NAME_ATTR = "name";
        private const string PARAM_TAG = "param";
        private const string TYPE_ATTR = "type";
        private const string VALUE_ATTR = "value";
        private TaskBase task;
        public string Name { get; internal set; }
        public double Interval { get; internal set; }
        public Type TaskType { get; internal set; }
        public bool Enabled { get; internal set; }
        public Priority Priority { get; internal set; }
        internal XmlNode ConfigurationNode { get; set; }

        public TaskBase CreateTask()
        {
            if (Enabled)
            {
                if (task == null)
                {
                    task = (TaskBase)Activator.CreateInstance(TaskType);
                    XmlNodeList childNodes = ConfigurationNode.SelectNodes(PARAM_TAG);
                    if (childNodes != null && childNodes.Count > 0)
                    {
                        foreach (XmlNode childNode in childNodes)
                        {
                            SetParameter(childNode, task);
                        }
                    }
                    task.Initialize(Name, Interval, Priority);
                }
                else
                {
                    return task;
                }
            }
            return null;
        }

        private static string SubstituteVariables(string value, IDictionary props)
        {
            var builder = new StringBuilder();
            int startIndex = 0;
            while (true)
            {
                int index = value.IndexOf("${", startIndex);
                if (index == -1)
                {
                    if (startIndex == 0)
                    {
                        return value;
                    }
                    builder.Append(value.Substring(startIndex, value.Length - startIndex));
                    return builder.ToString();
                }
                builder.Append(value.Substring(startIndex, index - startIndex));
                int endIndex = value.IndexOf('}', index);
                if (endIndex == -1)
                {
                    throw new ConfigurationErrorsException("[" + value +
                                                           "] has no closing brace. Opening brace at position [" + index +
                                                           "]");
                }
                index += 2;
                string propName = value.Substring(index, endIndex - index);
                var propValue = props[propName] as string;
                if (propValue != null)
                {
                    builder.Append(propValue);
                }
                startIndex = endIndex + 1;
            }
        }
        private static bool CanConvertTypeTo(Type sourceType, Type targetType)
        {
            if ((sourceType == null) || (targetType == null))
            {
                return false;
            }
            if (targetType.IsAssignableFrom(sourceType))
            {
                return true;
            }
            return false;
        }
        private static object ConvertTypeTo(object sourceInstance, Type targetType)
        {
            Type c = sourceInstance.GetType();
            if (targetType.IsAssignableFrom(c))
            {
                return sourceInstance;
            }
            return null;
        }

        private static object ParseEnum(Type enumType, string value, bool ignoreCase)
        {
            return Enum.Parse(enumType, value, ignoreCase);
        }

        private static object ConvertStringTo(Type target, string txt)
        {
            if (target == null)
            {
                throw new ArgumentNullException("target");
            }
            if ((typeof(string) == target) || (typeof(object) == target))
            {
                return txt;
            }
            if (target.IsEnum)
            {
                return ParseEnum(target, txt, true);
            }
            MethodInfo method = target.GetMethod("Parse", new[] { typeof(string) });
            if (method != null)
            {
                return method.Invoke(null, BindingFlags.InvokeMethod, null, new object[] { txt }, CultureInfo.InvariantCulture);
            }
            return null;
        }

        private static bool HasAttributesOrElements(XmlNode element)
        {
            foreach (XmlNode node in element.ChildNodes)
            {
                if ((node.NodeType == XmlNodeType.Attribute) || (node.NodeType == XmlNodeType.Element))
                {
                    return true;
                }
            }
            return false;
        }

        private static bool IsTypeConstructible(Type type)
        {
            if (type.IsClass && !type.IsAbstract)
            {
                ConstructorInfo constructor = type.GetConstructor(new Type[0]);
                if (((constructor != null) && !constructor.IsAbstract) && !constructor.IsPrivate)
                {
                    return true;
                }
            }
            return false;
        }

        private static object CreateObjectFromXml(XmlNode element, Type defaultTargetType, Type typeConstraint)
        {
            Type c;
            string attribute = element.GetAttribute(TYPE_ATTR);
            if (string.IsNullOrEmpty(attribute))
            {
                if (defaultTargetType == null)
                {
                    throw new ConfigurationErrorsException("Object type not specified. Cannot create object of type [" + typeConstraint.FullName + "]. Missing Value or Type.");
                }
                c = defaultTargetType;
            }
            else
            {
                try
                {
                    c = TaskSectionHandler.GetTypeFromString(attribute, true, true);
                }
                catch (Exception exception)
                {
                    throw new ConfigurationErrorsException("Failed to find type [" + attribute + "]", exception);
                }
            }
            if ((typeConstraint != null) && !typeConstraint.IsAssignableFrom(c))
            {
                if (!CanConvertTypeTo(c, typeConstraint))
                {
                    throw new ConfigurationErrorsException("Object type [" + c.FullName + "] is not assignable to type [" + typeConstraint.FullName + "]. There are no acceptable type conversions.");
                }
            }
            object target;
            try
            {
                target = Activator.CreateInstance(c);
            }
            catch (Exception e)
            {
                throw new ConfigurationErrorsException("Failed to construct object of type [" + c.FullName + "] Exception: " + e);
            }
            foreach (XmlNode node in element.ChildNodes)
            {
                if (node.NodeType == XmlNodeType.Element)
                {
                    SetParameter(node, target);
                }
            }
            return target;

        }

        private static void SetParameter(XmlNode element, object target)
        {
            string attribute = element.RemoveAttribute(NAME_ATTR);
            if (((element.LocalName != "param") || (attribute == null)) || (attribute.Length == 0))
            {
                attribute = element.LocalName;
            }
            Type targetType = target.GetType();
            PropertyInfo property = targetType.GetProperty(attribute,
                                                           BindingFlags.NonPublic | BindingFlags.Public |
                                                           BindingFlags.Instance | BindingFlags.IgnoreCase);
            Type propertyType = property.PropertyType;
            string innerText = element.RemoveAttribute(VALUE_ATTR);
            if (innerText == null && element.HasChildNodes)
            {
                foreach (XmlNode node in element.ChildNodes)
                {
                    if ((node.NodeType == XmlNodeType.CDATA) || (node.NodeType == XmlNodeType.Text))
                    {
                        if (innerText == null)
                        {
                            innerText = node.InnerText;
                        }
                        else
                        {
                            innerText = innerText + node.InnerText;
                        }
                    }
                }
            }
            if (innerText != null)
            {
                try
                {
                    innerText = SubstituteVariables(innerText, Environment.GetEnvironmentVariables());
                }
                catch (SecurityException)
                {
                }
                Type type3 = null;
                string typeName = element.GetAttribute(TYPE_ATTR);
                if (!string.IsNullOrEmpty(typeName))
                {
                    try
                    {
                        Type c = TaskSectionHandler.GetTypeFromString(typeName, true, true);
                        if (!propertyType.IsAssignableFrom(c))
                        {
                            if (CanConvertTypeTo(c, propertyType))
                            {
                                type3 = propertyType;
                                propertyType = c;
                            }
                            else
                            {
                                throw new ConfigurationErrorsException("Subtype [" + c.FullName + "] set on [" +
                                                                       attribute +
                                                                       "] is not a subclass of property type [" +
                                                                       propertyType.FullName +
                                                                       "] and there are no acceptable type conversions.");
                            }
                        }
                        else
                        {
                            propertyType = c;
                        }
                    }
                    catch (Exception exception)
                    {
                        throw new ConfigurationErrorsException(
                            "Failed to find type [" + typeName + "] set on [" + attribute + "]", exception);
                    }
                }
                object sourceInstance = ConvertStringTo(propertyType, innerText);
                if ((sourceInstance != null) && (type3 != null))
                {
                    sourceInstance = ConvertTypeTo(sourceInstance, type3);
                }
                if (sourceInstance != null)
                {
                    try
                    {
                        property.SetValue(target, sourceInstance, BindingFlags.SetProperty, null, null,
                                          CultureInfo.InvariantCulture);
                    }
                    catch (TargetInvocationException e)
                    {
                        throw new ConfigurationErrorsException(
                            "Failed to set parameter [" + property.Name + "] on object [" +
                            target + "] using value [" + sourceInstance + "]", e.InnerException);
                    }
                }
                else
                {
                    throw new ConfigurationErrorsException("Unable to set property [" + attribute + "] on object [" +
                                                           target + "] using value [" + innerText +
                                                           "] (with acceptable conversion types)");
                }
            }
            else
            {
                object propValue;
                if ((propertyType == typeof(string)) && !HasAttributesOrElements(element))
                {
                    propValue = "";
                }
                else
                {
                    Type defaultTargetType = null;
                    if (IsTypeConstructible(propertyType))
                    {
                        defaultTargetType = propertyType;
                    }
                    propValue = CreateObjectFromXml(element, defaultTargetType, propertyType);
                }
                if (propValue == null)
                {
                    throw new ConfigurationErrorsException("Failed to create object to set param: " + attribute);
                }
                try
                {
                    property.SetValue(target, propValue, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture);
                }
                catch (TargetInvocationException e)
                {
                    throw new ConfigurationErrorsException(
                        "Failed to set parameter [" + property.Name + "] on object [" + target + "] using value [" +
                        propValue + "]", e.InnerException);
                }
            }
        }
    }

    public class TaskCollection : Collection<TaskConfiguration>
    {
    }
}
